package com.jrx.anytxn.common.uitl;

import com.jrx.anytxn.common.exception.TxnException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 文件工具类
 *
 * @author liuyang
 * @date 2021-03-17
 */
public class FileUtils {

    private static final Logger logger = LoggerFactory.getLogger(FileUtils.class);

    /**
     * 创建目录
     *
     * @param dir 目录
     * @return File
     * @throws TxnException
     */
    public static File createDirectories(String dir) throws TxnException {
        Path pDir = Paths.get(dir);
        if (!Files.isDirectory(pDir)) {
            try {
                Files.createDirectories(pDir);
            } catch (IOException e) {
                logger.error("创建目录[{}]失败：{}", dir, e);
                throw new TxnException("创建目录[" + dir + "]失败：" + e.getMessage());
            }
        }
        return pDir.toFile();
    }

    /**
     * 创建文件
     *
     * @param filePath 文件的全路径
     * @return File
     * @throws TxnException
     */
    public static File createFile(String filePath) throws TxnException {
        Path pFilePath = Paths.get(filePath);
        if (!Files.exists(pFilePath)) {
            try {
                Files.createFile(pFilePath);
            } catch (IOException e) {
                logger.error("创建文件[{}]失败：{}", filePath, e);
                throw new TxnException("创建文件[" + filePath + "]失败：" + e.getMessage());
            }
        }
        return pFilePath.toFile();
    }

    /**
     * 创建文件
     *
     * @param dir      目录
     * @param fileName 文件名
     * @return File
     * @throws TxnException
     */
    public static File createFile(String dir, String fileName) throws TxnException {
        createDirectories(dir);
        String filePath = dir + File.separator + fileName;
        return createFile(filePath);
    }

    /**
     * 删除文件
     *
     * @param filePath 文件全路径
     * @return boolean
     * @throws TxnException
     */
    public static boolean deleteFile(String filePath) throws TxnException {
        Path pFilePath = Paths.get(filePath);
        try {
            return Files.deleteIfExists(pFilePath);
        } catch (IOException e) {
            logger.error("删除文件[{}]失败：{}", filePath, e);
            throw new TxnException("删除文件[" + filePath + "]失败：" + e.getMessage());
        }
    }

    /**
     * 查询目录下的文件名列表。
     *
     * @param dir 目录
     * @return List<String>
     * @throws TxnException
     */
    public static List<String> findFileNames(String dir) throws TxnException {
        Path pDir = Paths.get(dir);
        try {
            List<Path> paths = Files.list(pDir).collect(Collectors.toList());
            List<File> files = paths.stream().map(path -> path.toFile()).collect(Collectors.toList());
            List<String> fileNames = files.stream().map(file -> file.getName()).collect(Collectors.toList());
            return fileNames;
        } catch (IOException e) {
            logger.error("查询目录[{}]的文件名列表失败：{}", dir, e);
            throw new TxnException("查询目录[" + dir + "]的文件名列表失败：" + e.getMessage());
        }
    }

    /**
     * 查询目录下的文件列表。
     *
     * @param dir 目录
     * @return List<File>
     * @throws TxnException
     */
    public static List<File> findFiles(String dir) throws TxnException {
        Path pDir = Paths.get(dir);
        try {
            if(!isExists(dir)){
                return new ArrayList<File>();
            }
            List<Path> paths = Files.list(pDir).collect(Collectors.toList());
            List<File> files = paths.stream().map(path -> path.toFile()).collect(Collectors.toList());
            return files;
        } catch (IOException e) {
            logger.error("查询目录[{}]文件失败：{}", dir, e);
            throw new TxnException("查询目录[" + dir + "]文件失败：" + e.getMessage());
        }
    }

    /**
     * 文件拷贝
     *
     * @param fromFilePath 源文件全路径
     * @param toFilePath   目标文件全路径
     * @return File
     * @throws TxnException
     */
    public static File copyFile(String fromFilePath, String toFilePath) throws TxnException {
        Path pFromFilePath = Paths.get(fromFilePath);
        Path pToFilePath = Paths.get(toFilePath);
        if (!Files.exists(pFromFilePath)) {
            logger.error("源文件[{}}]不存在", fromFilePath);
            throw new TxnException("源文件[{" + fromFilePath + "}}]不存在");
        }

        try {
            Files.copy(pFromFilePath, pToFilePath, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            logger.error("文件[{}]拷贝到[{}]失败：{}", fromFilePath, toFilePath, e);
            throw new TxnException("文件[" + fromFilePath + "]拷贝到[" + toFilePath + "]失败：" + e.getMessage());
        }

        return pToFilePath.toFile();
    }

    /**
     * 文件拷贝
     *
     * @param fromPath 源目录
     * @param toPath   目标目录
     * @param fileName 源文件
     * @return File
     * @throws TxnException
     */
    public static File copyFile(String fromPath, String toPath, String fileName) throws TxnException {
        String fromFilePath = fromPath + File.separator + fileName;
        String toFilePath = toPath + File.separator + fileName;
        Path pFromFilePath = Paths.get(fromFilePath);
        Path pToFilePath = Paths.get(toFilePath);
        Path pToPath = Paths.get(toPath);
        if (!Files.exists(pFromFilePath)) {
            logger.error("源文件[{}}]不存在", fromFilePath);
            throw new TxnException("源文件[{" + fromFilePath + "}}]不存在");
        }
        if (!Files.isDirectory(pToPath)) {
            logger.error("目标目录[{}}]不存在", pToPath);
            throw new TxnException("目标目录[{" + pToPath + "}}]不存在");
        }

        try {
            Files.copy(pFromFilePath, pToFilePath, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            logger.error("文件[{}]从目录[{}]拷贝到目录[{}]失败：{}", fileName, fromPath, toPath, e);
            throw new TxnException("文件[" + fileName + "]从目录[" + fromPath + "]拷贝到目录[" + toPath + "]失败：" + e.getMessage());
        }

        return pToFilePath.toFile();
    }

    /**
     * 向文件中追加写入数据，如果文件不存在，则会创建
     *
     * @param filePath 文件
     * @param datas    写入数据
     * @param charset  字符集，为空默认的就是UTF-8
     * @return File
     * @throws TxnException
     */
    public static File writeFile(String filePath, List<String> datas, String charset) throws TxnException {

        charset = StringUtils.isBlank(charset) ? "UTF-8" : charset;
        Path pFilePath = Paths.get(filePath);
        try (OutputStream out = Files.newOutputStream(pFilePath, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
            for (int i = 0; i < datas.size(); i++) {
                String data = datas.get(i);
                if (i == 0) {
                    data = sizeOf(filePath) > 0 ? "\r\n" + data : data;
                }
                data = i != datas.size() - 1 ? data + "\r\n" : data;
                byte[] dataBytes = data.getBytes(charset);
                int len = dataBytes.length;
                int rem = len;
                while (rem > 0) {
                    int n = Math.min(rem, 1024);
                    out.write(dataBytes, (len - rem), n);
                    rem -= n;
                }
            }
        } catch (IOException e) {
            logger.error("文件[{}]写入数据失败：{}", filePath, e);
            throw new TxnException("文件[" + filePath + "]写入数据失败：" + e.getMessage());
        }

        return pFilePath.toFile();
    }

    /**
     * 文件合并
     *
     * @param targetFilePath  目标文件
     * @param sourceFilePaths 待合并文件
     * @throws TxnException
     */
    public static void mergeFile(String targetFilePath, List<String> sourceFilePaths) throws TxnException {

        for (String sourceFilePath : sourceFilePaths) {
            if (!isExists(sourceFilePath)) {
                logger.error("源文件[{}]不存在。", sourceFilePath);
                throw new TxnException("源文件[" + sourceFilePath + "]不存在");
            }
        }

        File targetFile = createFile(targetFilePath);

        try (FileChannel outChannel = new FileOutputStream(targetFile, false).getChannel()) {
            ByteBuffer buf = ByteBuffer.allocate(1024);
            for (int i = 0; i < sourceFilePaths.size(); i++) {
                String sourceFilePath = sourceFilePaths.get(i);
                if (sizeOf(sourceFilePath) <=0) {
                    continue;
                }
                try (FileChannel inChannel = new FileInputStream(sourceFilePath).getChannel()) {
                    buf.clear();
                    if (sizeOf(targetFilePath) > 0) {
                        buf.put("\r\n".getBytes());
                    }
                    while (inChannel.read(buf) != -1) {
                        buf.flip();
                        outChannel.write(buf);
                        buf.clear();
                    }
                } catch (IOException e) {
                    logger.error("文件[{}]写入文件[{}]失败：{}", sourceFilePath, targetFilePath, e);
                    throw new TxnException("文件[" + sourceFilePath + "]写入文件[" + targetFilePath + "]失败：" + e.getMessage());
                }
            }
        } catch (IOException e) {
            logger.error("合并文件[{}]失败：{}", targetFilePath, e);
            throw new TxnException("合并文件[" + targetFilePath + "]失败：" + e.getMessage());
        }
    }

    /**
     * 判断文件或者目录是否存在
     *
     * @param filePath 文件路径或者目录路径
     * @return boolean
     */
    public static boolean isExists(String filePath) {
        Path pfilePath = Paths.get(filePath);
        return Files.exists(pfilePath) || Files.isDirectory(pfilePath);
    }

    /**
     * 获取文件大小
     *
     * @param file
     * @return long
     * @throws TxnException
     */
    public static long sizeOf(File file) throws TxnException {
        if (!file.exists()) {
            logger.error("文件[{}]不存在", file.getAbsolutePath());
            throw new TxnException("文件[" + file.getAbsolutePath() + "]不存在");
        }
        if (file.isDirectory()) {
            logger.error("文件[{}]是目录", file.getAbsolutePath());
            throw new TxnException("文件[" + file.getAbsolutePath() + "]是目录");
        }
        return file.length();
    }

    /**
     * 获取文件大小
     *
     * @param filePath
     * @return long
     * @throws TxnException
     */
    public static long sizeOf(String filePath) throws TxnException {
        File file = new File(filePath);
        return sizeOf(file);
    }

    public static void main(String[] args) throws TxnException {
//        System.out.println(createDirectories("e:/test/dir/"));
//        System.out.println(createFile("e:/test/dir/","1.txt"));
//        System.out.println(createFile("e:/test/dir/","2.txt"));
//        System.out.println(createFile("e:/test/dir/","3.txt"));
//        System.out.println(createFile("e:/test/dir/","1.txt"));
//        System.out.println(deleteFile("e:/test/dir/1.txt"));
//        System.out.println(findFiles("e:/test/dir/"));
//        copyFile("e:/test/dir/", "e:/test/todir/", "2.txt");
//        copyFile("e:/test/dir/", "e:/test/todir/", "3.txt");
//        System.out.println(isExists("e:/test/dir/"));
//        System.out.println(isExists("e:/test/dir/2.txt"));
//        System.out.println(isExists("e:/test/dir/1.txt"));
//        System.out.println(isExists("e:/test/dir1/"));
//        writeFile("e:/test/dir/writeFileTest.txt", Arrays.asList(
//                "55国国  System.out.println(createDirectories(国国test国dir国))55;",
//                "国国  System.out.println(createFile(国国test国dir国国1.国));",
//                "国国  System.out.println(createFile(国国test国dir国国2.国));",
//                "国国  System.out.println(createFile(国国test国dir国国3.国));",
//                "国国  System.out.println(createFile(国国test国dir国国1.国));",
//                "国国  System.out.println(deleteFile(国国test国dir国1.国));",
//                "国国  System.out.println(findFiles(国国test国dir国));",
//                "国国  copyFile(国国test国dir国, 国国test国todir国国2.国);",
//                "国国  copyFile(国国test国dir国, 国国test国todir国国3.国);",
//                "国国  System.out.println(isExists(国国test国dir国));",
//                "国国  System.out.println(isExists(国国test国dir国2.国));",
//                "国国  System.out.println(isExists(国国test国dir国1.国));",
//                "55国国  System.out.println(isExists(国国test国dir1国));55"), "");
//        mergeFile("e:/test/dir/mergeFile1.txt", Arrays.asList("e:/test/dir/1.txt", "e:/test/dir/2.txt","e:/test/dir/3.txt","e:/test/dir/4.txt"));
//        System.out.println(sizeOf("e:/test/dir/"));
    }
}
